using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;

namespace IUPUI.StellarResults
{
	/// <summary>
	/// Represents an Observatory.
	/// </summary>
	[Serializable]
	public class Observatory : Identifiable,  IAltazimuthContainer,
		ININ.GIS.ICoordinatesContainer, IDisposable, System.ComponentModel.INotifyPropertyChanged
	{
		#region Private Properties
		private System.Timers.Timer m_ChangePositionTimer = new System.Timers.Timer();
		private DateTimeInterval m_ChangePositionInterval =
			new DateTimeInterval(1, ININ.DateTimeUnit.Seconds);
		private string m_Url = String.Empty;
		private ININ.GIS.Address m_Address = new ININ.GIS.Address();
		private ININ.GIS.Coordinates m_Coordinates = new ININ.GIS.Coordinates();
		private decimal m_EarthAltitude = 0;
		private int m_UTCOffset;
		private int m_CommunicationDelayToCentralComputer = 500;

		// Telescope properties
		private Altazimuth m_Altazimuth = new Altazimuth(90, 0);
		private bool m_IsPositionable = true;
		private double m_ViewAngle = 5;
		private int m_AltitudeMovePerSecond = 5;
		private int m_AzimuthMovePerSecond = 5;
		private string m_AstronomicalObjectBeingTracked = null;

		// Movement temporary varialbles
		private MoveDirection m_AltitudeMoveDirection = MoveDirection.None;
		private MoveDirection m_AzimuthMoveDirection = MoveDirection.None;
		private double m_TimerDelay = 0;
		private double m_AltitudeToMoveTo = 0;
		private double m_AzimuthToMoveTo = 0;

		private ObservatoryPositionChangeEventHandler m_ObservatoryPositionChanged;
		private EventHandler<ININ.EventArgs<Reading[]>> m_ReadingsTaken;
		private EventHandler<ININ.EventArgs<double>> m_ObservatoryViewAngleChanged;
		private EventHandler<ININ.EventArgs<string>> m_AstronomicalObjectBeingTrackedChanged;
		#endregion Private Properties

		#region Public Events
		/// <summary>
		/// Occurs when [reading taken].
		/// </summary>
		public event EventHandler<ININ.EventArgs<Reading[]>> ReadingsTaken
		{
			add
			{
				m_ReadingsTaken += value;
			}
			remove
			{
				m_ReadingsTaken -= value;
			}
		}

		/// <summary>
		/// Occurs when observatory position changed.
		/// </summary>
		public event ObservatoryPositionChangeEventHandler ObservatoryPositionChanged
		{
			add
			{
				m_ObservatoryPositionChanged += value;
			}
			remove
			{
				m_ObservatoryPositionChanged -= value;
			}
		}

		/// <summary>
		/// Occurs when observatory view angle changed.
		/// </summary>
		public event EventHandler<ININ.EventArgs<double>> ObservatoryViewAngleChanged
		{
			add
			{
				m_ObservatoryViewAngleChanged += value;
			}
			remove
			{
				m_ObservatoryViewAngleChanged -= value;
			}
		}

		/// <summary>
		/// Occurs when the astronomical object being tracked has changed.
		/// </summary>
		public event EventHandler<ININ.EventArgs<string>> AstronomicalObjectBeingTrackedChanged
		{
			add
			{
				m_AstronomicalObjectBeingTrackedChanged += value;
			}
			remove
			{
				m_AstronomicalObjectBeingTrackedChanged -= value;
			}
		}
		#endregion Public Events

		#region Public Properties
		/// <summary>
		/// Gets or sets the URL.
		/// </summary>
		/// <value>The URL.</value>
		public string Url
		{
			get
			{
				return m_Url;
			}
			set
			{
				m_Url = value;
				OnPropertyChanged("Url");
			}
		}

		/// <summary>
		/// Gets or sets the address where the observatory is located.
		/// </summary>
		/// <value>The address.</value>
		[Category("Observatory")]
		[Description("Gets or sets the address.")]
		public ININ.GIS.Address Address
		{
			get
			{
				return m_Address;
			}
			set
			{
				m_Address = value;
				OnPropertyChanged("Address");
			}
		}

		/// <summary>
		/// Gets or sets the <see cref="Altazimuth"/> of the telescope. 
		/// The azimuth will typically be between 0 and 360 degrees, 
		/// and the altitude typically will be between 0 and 90 degrees.
		/// </summary>
		/// <value>The degree of orientation.</value>
		[Category("Telescope")]
		[Description("Gets or sets the Altazimuth of the telescope. The azimuth will typically be between 0 and 360 degrees, and the altitude typically will be between 0 and 90 degrees.")]
		public Altazimuth Altazimuth
		{
			get
			{
				return m_Altazimuth;
			}
			set
			{
				// Only set if the values arent comparable
				if (m_Altazimuth.CompareTo(value) != 0)
				{
					m_Altazimuth = value;
					OnPropertyChanged("Altazimuth");

					OnObservatoryPositionChanged();
				}
			}
		}


		/// <summary>
		/// Gets or sets the change position interval.
		/// </summary>
		/// <value>The change position interval.</value>
		[Category("Telescope")]
		[Description("Gets or sets the change position interval.")]
		[Browsable(false)]
		public DateTimeInterval ChangePositionInterval
		{
			get
			{
				return m_ChangePositionInterval;
			}
			set
			{
				m_ChangePositionInterval = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether the telescope is positionable.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if the telescope is positionable; otherwise, <c>false</c>.
		/// </value>
		[Category("Telescope")]
		[Description("Gets or sets a value indicating whether the telescope is positionable.")]
		public bool IsPositionable
		{
			get
			{
				return m_IsPositionable;
			}
			set
			{
				m_IsPositionable = value;
				OnPropertyChanged("IsPositionable");
			}
		}

		/// <summary>
		/// Gets or sets the altitude at which the telescope sits.
		/// </summary>
		/// <value>The altitude.</value>
		[Category("Observatory")]
		[Description("Gets or sets the altitude at which the telescope sits.")]
		public decimal EarthAltitude
		{
			get
			{
				return m_EarthAltitude;
			}
			set
			{
				m_EarthAltitude = value;
				OnPropertyChanged("EarthAltitude");
			}
		}

		/// <summary>
		/// Gets or sets the coordinates where the observatory is located.
		/// </summary>
		/// <value>The coordinates.</value>
		[Category("Observatory")]
		[Description("Gets or sets the coordinates where the observatory is located.")]
		public ININ.GIS.Coordinates Coordinates
		{
			get
			{
				return m_Coordinates;
			}
			set
			{
				m_Coordinates = value;
				OnPropertyChanged("Coordinates");
			}
		}

		/// <summary>
		/// Gets or sets the UTC offset.
		/// </summary>
		/// <value>The UTC offset.</value>
		[Category("Observatory")]
		[Description("Gets or sets the UTC offset.")]
		public int UTCOffset
		{
			get
			{
				return m_UTCOffset;
			}
			set
			{
				m_UTCOffset = value;
				OnPropertyChanged("UTCOffset");
			}
		}

		/// <summary>
		/// Gets or sets the communication delay to central computer in milliseconds.
		/// </summary>
		/// <value>The communication delay to central computer in milliseconds.</value>
		[Category("Observatory")]
		[Description("Gets or sets the communication delay to central computer in milliseconds.")]
		public int CommunicationDelayToCentralComputer
		{
			get
			{
				return m_CommunicationDelayToCentralComputer;
			}
			set
			{
				m_CommunicationDelayToCentralComputer = value;
				OnPropertyChanged("CommunicationDelayToCentralComputer");
			}
		}

		/// <summary>
		/// Gets or sets the altitude angle move per second.
		/// </summary>
		/// <value>The altitude angle move per second.</value>
		[Category("Telescope")]
		[Description("Gets or sets the altitude move per second.")]
		public int AltitudeMovePerSecond
		{
			get
			{
				return m_AltitudeMovePerSecond;
			}
			set
			{
				m_AltitudeMovePerSecond = value;
				OnPropertyChanged("AltitudeMovePerSecond");
			}
		}

		/// <summary>
		/// Gets or sets the azimuth angle move per second.
		/// </summary>
		/// <value>The azimuth mangle ove per second.</value>
		[Category("Telescope")]
		[Description("Gets or sets the azimuth move per second.")]
		public int AzimuthMovePerSecond
		{
			get
			{
				return m_AzimuthMovePerSecond;
			}
			set
			{
				m_AzimuthMovePerSecond = value;
				OnPropertyChanged("AzimuthMovePerSecond");
			}
		}

		/// <summary>
		/// Gets or sets the view angle. (>0 - 90 degrees)
		/// </summary>
		/// <value>The view angle.</value>
		[Category("Telescope")]
		[Description("Gets or sets the view angle. (>0 - 90 degrees)")]
		public double ViewAngle
		{
			get
			{
				return m_ViewAngle;
			}
			set
			{
				m_ViewAngle = value;
				OnPropertyChanged("ViewAngle");
				OnObservatoryViewAngleChanged();
			}
		}

		/// <summary>
		/// Gets a value indicating whether this instance is changing position.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance is changing position; otherwise, <c>false</c>.
		/// </value>
		[Category("Telescope")]
		public bool IsChangingPosition
		{
			get
			{
				return m_ChangePositionTimer.Enabled;
			}
		}

		/// <summary>
		/// Gets or sets the astronomical object being tracked.
		/// </summary>
		/// <value>The astronomical object being tracked.</value>
		[Category("Telescope")]
		[Browsable(false)]
		[System.Xml.Serialization.XmlIgnore]
		public string AstronomicalObjectBeingTracked
		{
			get
			{
				return m_AstronomicalObjectBeingTracked;
			}
			set
			{
				if (m_AstronomicalObjectBeingTracked != value)
				{
					m_AstronomicalObjectBeingTracked = value;
					OnPropertyChanged("AstronomicalObjectBeingTracked");
					OnAstronomicalObjectBeingTrackedChanged();
				}
			}
		}
		#endregion Public Properties

		#region Constructors
		/// <summary>
		/// Initializes a new instance of the <see cref="Observatory"/> class.
		/// </summary>
		public Observatory()
		{
			m_ChangePositionTimer.Interval = 1000; // 1 second
			m_ChangePositionTimer.Elapsed += 
				new System.Timers.ElapsedEventHandler(m_Timer_Elapsed);
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="Observatory"/> class.
		/// </summary>
		/// <param name="ID">The ID.</param>
		public Observatory(
			string ID)
			: this()
		{
			ININ.Guard.ArgumentNotNullOrEmptyString(ID, "ID");
			m_ID = ID;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="Observatory"/> class.
		/// </summary>
		/// <param name="ID">The ID.</param>
		/// <param name="name">The name.</param>
		public Observatory(
			string ID,
			string name)
			: this(ID)
		{
			m_Name = name;
		}
		#endregion Constructors

		#region Event Launchers
		/// <summary>
		/// Called when [property changed].
		/// </summary>
		/// <param name="propertyName">Name of the property.</param>
		protected virtual void OnPropertyChanged(
			string propertyName)
		{
			if (m_PropertyChanged != null)
			{
				m_PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
			}
		}

		/// <summary>
		/// Called when [observatory position changed].
		/// </summary>
		protected virtual void OnObservatoryPositionChanged()
		{
			if (m_ObservatoryPositionChanged != null)
			{
				m_ObservatoryPositionChanged(
					this,
					new ObservatoryPositionChangeEventArgs(
						Altazimuth));
			}
		}

		/// <summary>
		/// Called when [reading taken].
		/// </summary>
		/// <param name="readings">The readings.</param>
		protected virtual void OnReadingsTaken(
			Reading[] readings)
		{
			// If the altazimuth is not set, then set it now
			foreach (Reading reading in readings)
			{
				if (reading.CurrentAltazimuth == null)
				{
					reading.CurrentAltazimuth = this.Altazimuth;
				}
			}

			if (m_ReadingsTaken != null)
			{
				m_ReadingsTaken(this, new ININ.EventArgs<Reading[]>(readings));
			}
		}

		/// <summary>
		/// Called when [observatory view angled changed].
		/// </summary>
		protected virtual void OnObservatoryViewAngleChanged()
		{
			if (m_ObservatoryViewAngleChanged != null)
			{
				m_ObservatoryViewAngleChanged(this, new ININ.EventArgs<double>(ViewAngle));
			}
		}

		/// <summary>
		/// Called when [astronomical object being tracked changed].
		/// </summary>
		protected virtual void OnAstronomicalObjectBeingTrackedChanged()
		{
			if (m_AstronomicalObjectBeingTrackedChanged != null)
			{
				m_AstronomicalObjectBeingTrackedChanged(this, new ININ.EventArgs<string>(AstronomicalObjectBeingTracked));
			}
		}
		#endregion Event Launchers

		/// <summary>
		/// Handles the Elapsed event of the m_Timer control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.Timers.ElapsedEventArgs"/> instance containing the event data.</param>
		private void m_Timer_Elapsed(
			object sender, 
			System.Timers.ElapsedEventArgs e)
		{
			// Get the interval based on the change position interval
			double interval = 
				ININ.DateTimeUtilities.GetTimerIntervalFromDateTimeUnit(
					m_ChangePositionInterval.Unit, 
					m_ChangePositionInterval.Value);

			// If the timer needs delayed, then subtract the time and delay if necessary
			if (m_TimerDelay > 0)
			{
				m_TimerDelay = m_TimerDelay - interval;

				if (m_TimerDelay > 0)
				{
					// If there is still delay, then exit and handle on next tick
					return;
				}
			}

			// Calculate how much movement for this interval (interval might not be per second)
			double altitudeMovePerInterval =
				m_AltitudeMovePerSecond * ((double)interval / 1000);
			double azimuthMovePerInterval =
				m_AzimuthMovePerSecond * ((double)interval / 1000);

			// When we are at the desired angles, stop the timer
			if (Altazimuth.Altitude == m_AltitudeToMoveTo
				&& Altazimuth.Azimuth == m_AzimuthToMoveTo)
			{
				StopChangePosition();
				return;
			}
			else
			{
				// Move altitude if necessary
				if (Altazimuth.Altitude != m_AltitudeToMoveTo)
				{
					if (Math.Abs(m_AltitudeToMoveTo - Altazimuth.Altitude) <= altitudeMovePerInterval)
					{
						Altazimuth.Altitude = m_AltitudeToMoveTo;
						m_AltitudeMoveDirection = MoveDirection.None;
					}
					else if (m_AltitudeMoveDirection == MoveDirection.Down)
					{
						Altazimuth.Altitude -= altitudeMovePerInterval;
					}
					else
					{
						Altazimuth.Altitude += altitudeMovePerInterval;
					}
				}

				// Move azimuth if necessary
				if (Altazimuth.Azimuth != m_AzimuthToMoveTo)	
				{
					if (Math.Abs(m_AzimuthToMoveTo - Altazimuth.Azimuth) <= azimuthMovePerInterval)
					{
						Altazimuth.Azimuth = m_AzimuthToMoveTo;
						m_AzimuthMoveDirection = MoveDirection.None;
					}
					else if (m_AzimuthMoveDirection == MoveDirection.Down)
					{
						Altazimuth.Azimuth -= azimuthMovePerInterval;
					}
					else
					{
						Altazimuth.Azimuth += azimuthMovePerInterval;
					}
				}

				// Ensure we are in range
				if (Altazimuth.Azimuth > 360)
				{
					Altazimuth.Azimuth -= 360;
				}
				// Ensure we are in range
				if (Altazimuth.Azimuth < 0)
				{
					Altazimuth.Azimuth = 360 - Altazimuth.Azimuth;
				}

				OnObservatoryPositionChanged();
			}
		}

		#region Public Methods
       /// <summary>
		/// Takes the readings of a collection of <see cref="AstronomicalObject"/>s.
		/// </summary>
		/// <param name="astronomicalObject">The astronomical object.</param>
		/// <param name="observationType">Type of the observation.</param>
		/// <param name="dateTimeUTC">The date time UTC.</param>
		/// <returns></returns>
        public Reading TakeReadings(
            AstronomicalObject astronomicalObject,
            ObservationType observationType,
            DateTime dateTimeUTC)
            {
            return TakeReadings(
                astronomicalObject,
                observationType,
                dateTimeUTC,
                dateTimeUTC);   //this is never called, should be commented out.
            }
        

		/// <summary>
		/// Takes the readings of a collection of <see cref="AstronomicalObject"/>s.
		/// </summary>
		/// <param name="astronomicalObject">The astronomical object.</param>
		/// <param name="observationType">Type of the observation.</param>
		/// <param name="dateTimeUTC">The date time UTC.</param>
		/// <param name="startDateTimeUTC">The start date time UTC.</param>
		/// <returns></returns>
		public Reading TakeReadings(
			AstronomicalObject astronomicalObject,
			ObservationType observationType,
			DateTime dateTimeUTC,
			DateTime startDateTimeUTC)
		{
			Reading[] readings = TakeReadings(
				new AstronomicalObject[] { astronomicalObject },
				observationType,
				dateTimeUTC,
				startDateTimeUTC);

			if (readings.Length > 0)
			{
				return readings[0];
			}

			return null;
		}

		/// <summary>
		/// Takes the readings of a collection of <see cref="AstronomicalObject"/>s.
		/// </summary>
		/// <param name="simulation">The simulation.</param>
		/// <param name="dateTimeUTC">The date time UTC.</param>
		/// <returns></returns>
		public Reading[] TakeReadings(
			Simulation simulation,
			DateTime dateTimeUTC)
		{
			return TakeReadings(
				simulation.AstronomicalObjects.ToArray(),
				simulation.ObservationType,
				dateTimeUTC,
                simulation.StartDateTime );
		}

		/// <summary>
		/// Takes the readings of a collection of <see cref="AstronomicalObject"/>s.
		/// </summary>
		/// <param name="astronomicalObjects">The astronomical objects.</param>
		/// <param name="observationType">Type of the observation.</param>
		/// <param name="dateTimeUTC">The date time UTC.</param>
		/// <param name="startDateTimeUTC">The start date time.</param>
		/// <returns></returns>
		public Reading[] TakeReadings(
			AstronomicalObject[] astronomicalObjects,
			ObservationType observationType,
			DateTime dateTimeUTC)
		{
			return TakeReadings(
				astronomicalObjects,
				observationType,
				dateTimeUTC,
				dateTimeUTC);
		}
		/// <summary>
		/// Takes the readings of a collection of <see cref="AstronomicalObject"/>s.
		/// </summary>
		/// <param name="astronomicalObjects">The astronomical objects.</param>
		/// <param name="observationType">Type of the observation.</param>
		/// <param name="dateTimeUTC">The date time UTC.</param>
		/// <param name="startDateTimeUTC">The start date time.</param>
		/// <returns></returns>
		public Reading[] TakeReadings(
			AstronomicalObject[] astronomicalObjects,
			ObservationType observationType,
			DateTime dateTimeUTC,
			DateTime startDateTimeUTC)
		{
			List<Reading> readings = new List<Reading>();

			// Get readings for each object
			foreach (AstronomicalObject ao in astronomicalObjects)
			{
				Altazimuth altazimuth = AstronomicalCalculations.ComputeAzimuthAngle(
							ao,
							this,
							observationType,
							dateTimeUTC,
							startDateTimeUTC);

				Reading reading =
					new Reading(
						dateTimeUTC,
						this.Altazimuth,
						altazimuth,
						ao,
						ao.Position,
						AstronomicalCalculations.CheckIfObjectIsVisible(
							altazimuth,
							this.Altazimuth,
							this.ViewAngle));
				readings.Add(reading);
			}

			// Record the readings
			Reading[] readingsArray = readings.ToArray();
			RecordReadings(readingsArray);

			return readingsArray;
		}

		/// <summary>
		/// Stops the change position.
		/// </summary>
		public void StopChangePosition()
		{
			m_AltitudeToMoveTo = Altazimuth.Altitude;
			m_AzimuthToMoveTo = Altazimuth.Azimuth;
			m_AltitudeMoveDirection = MoveDirection.None;
			m_AzimuthMoveDirection = MoveDirection.None;
			m_ChangePositionTimer.Stop();
		}

		/// <summary>
		/// Changes the position. (Assumes using latency to central computer.)
		/// </summary>
		/// <param name="altazimuth">The altazimuth.</param>
		public void ChangePosition(
			Altazimuth altazimuth)
		{
			ChangePosition(
				altazimuth.Azimuth,
				altazimuth.Altitude,
				true);
		}

		/// <summary>
		/// Changes the position. (Assumes using latency to central computer.)
		/// </summary>
		/// <param name="altazimuth">The altazimuth.</param>
		/// <param name="useDelayToComputer">if set to <c>true</c> [use delay to computer].</param>
		public void ChangePosition(
			Altazimuth altazimuth,
			bool useDelayToComputer)
		{
			ChangePosition(
				altazimuth.Azimuth,
				altazimuth.Altitude,
				useDelayToComputer);
		}

		/// <summary>
		/// Changes the position.
		/// </summary>
		/// <param name="azimuth">The azimuth.</param>
		/// <param name="altitude">The altitude.</param>
		/// <param name="useDelayToComputer">if set to <c>true</c> [use delay to computer].</param>
		public void ChangePosition(
			double azimuth,
			double altitude,
			bool useDelayToComputer)
		{
			if (Altazimuth.Altitude != altitude
				|| Altazimuth.Azimuth != azimuth)
			{
				// Calculate delay to start movement timer
				m_TimerDelay = 0;
				if (useDelayToComputer)
				{
					m_TimerDelay += m_CommunicationDelayToCentralComputer;
				}

				// Set angles to move to
				m_AzimuthToMoveTo = azimuth;
				m_AltitudeToMoveTo = altitude;

				// Calculate move directions 
				// (We use move directions because sometimes its faster to go up or down depending 
				// on the values, such as moving from 350 to 0 is faster to go up than down.)
				if (Altazimuth.Altitude != m_AltitudeToMoveTo)
				{
					if (m_AltitudeToMoveTo < Altazimuth.Altitude)
					{
						m_AltitudeMoveDirection = MoveDirection.Down;
					}
					else
					{
						m_AltitudeMoveDirection = MoveDirection.Up;
					}
				}
				else
				{
					m_AltitudeMoveDirection = MoveDirection.None;
				}
				// TODO: Find out how to better calculate differences here
				if (Altazimuth.Azimuth != m_AzimuthToMoveTo)
				{
					if (m_AzimuthToMoveTo < Altazimuth.Azimuth)
					{
						m_AzimuthMoveDirection = MoveDirection.Down;
					}
					else
					{
						m_AzimuthMoveDirection = MoveDirection.Up;
					}
				}

				// Start timer
				m_ChangePositionTimer.Start();
			}
			else
			{
				// Stop timer if we are already at the coordinates
				if (m_ChangePositionTimer.Enabled)
				{
					m_ChangePositionTimer.Stop();
				}
			}
		}

		/// <summary>
		/// Records the reading.
		/// </summary>
		/// <param name="reading">The reading.</param>
		public void RecordReading(
			Reading reading)
		{
			RecordReadings(new Reading[] { reading });
		}

		/// <summary>
		/// Records the reading.
		/// </summary>
		/// <param name="reading">The reading.</param>
		public void RecordReadings(
			Reading[] readings)
		{
			OnReadingsTaken(readings);
		}
		#endregion Public Methods

		/// <summary>
		/// Gets the inverse on circle.
		/// </summary>
		/// <param name="degrees">The degrees.</param>
		/// <returns></returns>
		private decimal GetInverseOnCircle(
			decimal degrees)
		{
			if (degrees > 360 || degrees < 0)
			{
				throw new ArgumentOutOfRangeException("degrees");
			}

			return -Math.Abs(360 - degrees);
		}

		#region IDisposable Members
		private bool m_IsDisposed = false;

		/// <summary>
		/// Gets a value indicating whether this instance is disposed.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance is disposed; otherwise, <c>false</c>.
		/// </value>
		[Browsable(false)]
		public bool IsDisposed
		{
			get
			{
				return m_IsDisposed;
			}
		}

		private event System.EventHandler m_Disposed;

		/// <summary>
		/// Occurs when this instance is disposed.
		/// </summary>
		public event System.EventHandler Disposed
		{
			add
			{
				m_Disposed += value;
			}
			remove
			{
				m_Disposed -= value;
			}
		}

		/// <summary>
		/// Releases unmanaged and - optionally - managed resources
		/// </summary>
		public void Dispose()
		{
			if (!m_IsDisposed)
			{
				this.Dispose(true);
				GC.SuppressFinalize(this);
			}
		}

		/// <summary>
		/// Releases unmanaged and - optionally - managed resources
		/// </summary>
		/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
		protected virtual void Dispose(bool disposing)
		{
			try
			{
				if (disposing)
				{
					m_PropertyChanged = null;
					m_ObservatoryPositionChanged = null;
					m_ObservatoryViewAngleChanged = null;
					m_AstronomicalObjectBeingTrackedChanged = null;

					if (m_ChangePositionTimer != null)
					{
						m_ChangePositionTimer.Stop();
						m_ChangePositionTimer.Dispose();
					}

					if (m_Disposed != null)
					{
						m_Disposed(this, EventArgs.Empty);
						m_Disposed = null;
					}
				}
			}
			finally
			{
				m_IsDisposed = true;
			}
		}
		#endregion IDisposable Members

		#region INotifyPropertyChanged Members
		private PropertyChangedEventHandler m_PropertyChanged;

		/// <summary>
		/// Occurs when [property changed].
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged
		{
			add
			{
				m_PropertyChanged += value;
			}
			remove
			{
				m_PropertyChanged -= value;
			}
		}	 
		#endregion INotifyPropertyChanged Members 
	}
}
